/*
 * $Id: client.c 7557b5d4d82b 2012/12/02 09:07:16 pgurumur $
 *
 * Copyright (c) 2006, 2007, 2008 Prabhu Gurumurthy <pgurumur@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "sshc.h"

static void kbd_callback(const char *, int, const char *, int, int, 
      const LIBSSH2_USERAUTH_KBDINT_PROMPT *,
      LIBSSH2_USERAUTH_KBDINT_RESPONSE *, void **);  

void
ssh_init(const char *host, const u_int16_t port, int af)
{
   int addr_error = 0;
   struct addrinfo hints, *res, *res0;

   logit(0, "in %s", __func__);
   log_init();
   sshc = (struct _sshc *)malloc(sizeof(struct _sshc));
   if (sshc == NULL)
      error(1, "%s: malloc()", __func__);

   assert(sshc);
   sshc->input = NULL;
   sshc->tempfile = NULL;

   if (port)
      sshc->port = port;
   else
      sshc->port = DEF_SSH_PORT;
   
   if (af)
      sshc->af = af;
   else
      sshc->af = 0;

   if (host)
      sshc->host = host;
   else
      error(0, "no hostname defined!");

#if LIBSSH2_VERSION_MAJOR == 1
#if LIBSSH2_VERSION_MINOR == 2
#if LIBSSH2_VERSION_PATCH > 6
   if (libssh2_init(0) != 0) 
      error(0, "libssh2_init failed!");
#endif
#endif
#endif

   memset(sshc->prikey, '\0', MAXLINE);
   memset(sshc->pubkey, '\0', MAXLINE);

   memset(&hints, 0, sizeof(hints));
   hints.ai_family = PF_UNSPEC;
   if (sshc->af)
      hints.ai_family = sshc->af;
      
   hints.ai_socktype = SOCK_STREAM;
   addr_error = getaddrinfo(sshc->host, DEF_SERVICE_NAME, &hints, &res0);
   if (addr_error)
      error(1, "%s: getaddrinfo(): %s", __func__, gai_strerror(addr_error));

   for (res = res0; res; res = res->ai_next) {
      sshc->sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
      if (sshc->sock < 0) {
         freeaddrinfo(res0);
         error(1, "%s: socket()", __func__);
      }

      logit(0, "socket fd: %d", sshc->sock);

      char buf[MAXLINE];
      memset(buf, '\0', MAXLINE);

      if (connect(sshc->sock, res->ai_addr, res->ai_addrlen) < 0)
         if (errno != EINPROGRESS) {
            freeaddrinfo(res0);
            error(1, "%s: connect()", __func__);
         }

      int retval = 0;
#ifdef __linux__
      /*
       * No sa_len defined in sockaddr, It seems to be optional in POSIX
       * Linux opt to not use it
       */
      retval = getnameinfo(res->ai_addr, res->ai_addrlen, buf, sizeof(buf),
            NULL, 0, NI_NUMERICHOST);
#else
      /*
       * MAC (which is essentially BSD and BSD's have sa_len defined in
       * sockaddr structure, making getnameinfo easier
       * Thanks to itojun for suggesting to use getnameinfo, I
       * was getting wound up in inet_ntop goo.
       */
      retval = getnameinfo(res->ai_addr, res->ai_addr->sa_len,
                  buf, sizeof(buf), NULL, 0, NI_NUMERICHOST);
#endif
      if (retval) 
         logit(0, "connected to %s", sshc->host);
      else
         logit(0, "connected to %s (%s)", sshc->host, buf);

      break;
   }

   if (res0)
      freeaddrinfo(res0);

   assert(sshc->sock);
}

void
ssh_session()
{ 

   logit(0, "in %s", __func__);
   int retval = 0;

   sshc->session = libssh2_session_init();
   if (sshc->session == NULL)
      error(0, "%s: libssh2_session_init() %s", __func__, ssh_geterror());
   
   logit(0, "libssh2_session_init");

   retval = libssh2_session_startup(sshc->session, sshc->sock);
   if (retval != 0)
      error(1,  "libssh2_session_startup() %s", ssh_geterror());

   logit(0, "libssh2_session_startup");
         
   retval = libssh2_banner_set(sshc->session, DEF_BANNER);
   if (retval != 0)
      error(0, "libssh2_banner_set()");

   logit(0, "set banner: %s", DEF_BANNER);

   sshc->version = libssh2_version(LIBSSH2_VERSION_NUM);
   logit(0, "libssh version: %s", sshc->version);

   LIBSSH2_KNOWNHOSTS *nh = libssh2_knownhost_init(sshc->session);
   if (nh == NULL)
      error(0, "libssh2_knownhost_init()");

   char *home = getenv("HOME");

   char hostfile[MAXLINE];
   memset(hostfile, '\0', MAXLINE);

   snprintf(hostfile, MAXLINE, "%s/.ssh/known_hosts", home);
   logit(0, "ssh known hosts file: %s", hostfile);
   libssh2_knownhost_readfile(nh, hostfile, LIBSSH2_KNOWNHOST_FILE_OPENSSH);
   
   size_t length = 0;
   int type = 0;
   const char *fingerprint = libssh2_session_hostkey(sshc->session,
         &length, &type);

   if (fingerprint) {
      struct libssh2_knownhost *host;
      int check = libssh2_knownhost_check(nh, sshc->host, fingerprint, length,
            LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW,
            &host);
      logit(0, "Host check: %d, key: %s", check,
            (check <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH) ? host->key : "<none>");
   }
   else {
      fingerprint = libssh2_hostkey_hash(
            sshc->session, LIBSSH2_HOSTKEY_HASH_SHA1);
   }

   libssh2_knownhost_free(nh);

   if (fingerprint) {
      char message[MAX_FINGERPRINT];
      memset(message, '\0', MAX_FINGERPRINT);
      int ix = 0;
      for (ix = 0; ix < MAX_FINGERPRINT; ix++) 
         snprintf(&message[ix],
               sizeof(ix) + 1, "%02X ", (unsigned char)fingerprint[ix]);

      logit(0, "%s fingerprint: %s", sshc->host, message);
   }
}

int
ssh_pkauth(const char *authlist)
{
   int retval = 0;
   if (authlist) {
      if (strstr(authlist, "publickey")) {
         logit(0, "using public key authentication");
         char prompt[MAXLINE];
         char *home = getenv("HOME");
         snprintf(sshc->prikey, MAXLINE, "%s/.ssh/id_rsa", home);
         snprintf(sshc->pubkey, MAXLINE, "%s/.ssh/id_rsa.pub", home);

         logit(0, "using private key: %s", sshc->prikey);
         logit(0, "using public key: %s", sshc->pubkey);

         memset(prompt, '\0', MAXLINE);
         snprintf(prompt, sizeof(prompt), "Enter passphrase for %s: ",
            sshc->prikey);
         sshc->keypass = read_pass(prompt);
         int rc = libssh2_userauth_publickey_fromfile(sshc->session, sshc->user,
            sshc->pubkey, sshc->prikey, sshc->keypass);
         if (rc == 0) {
            logit(0, "publickey authentication succeeded");
            retval = 1;
         }
         else
            logit(0, "publickey authentication failed!");
      }
   }

   return retval;
}

int
ssh_kiauth(const char *authlist)
{
   int retval = 0;
   if (authlist) {
      if (strstr(authlist, "keyboard-interactive")) {
         logit(0, "using keyboard-interactive authentication");
         int rc = libssh2_userauth_keyboard_interactive(sshc->session,
               sshc->user, &kbd_callback);
         if (rc == 0) {
            logit(0, "keyboard-interactive authentication succeeded");
            retval = 1;
         }
         else
            logit(0, "keyboard-interactive authentication succeeded");
      }
   }

   return retval;
}

int
ssh_passauth(const char *authlist, const char *password)
{
   int retval = 0;
   const char *pass = NULL;
   if (authlist) {
      if (strstr(authlist, "password")) {
         if (password)
            pass = password;
         else {
            char prompt[MAXLINE];
            memset(prompt, '\0', MAXLINE);
            snprintf(prompt, sizeof(prompt), "Enter password for %s on %s: ",
                  sshc->user, sshc->host); 
            pass = read_pass(prompt);
         }
      }
   }
   else {
      if (password) {
         pass = password;
      }
   }

   if (pass) {
      logit(0, "using password authentication");
      int rc = libssh2_userauth_password(sshc->session, sshc->user, pass);
      if (rc == 0) {
         logit(0, "password authentication succeeded");
         retval = 1;
      }
      else
         logit(0, "password authentication failed!");
   }

   return retval;
}

void
ssh_userauth(const char *username, const char *password)
{
   logit(0, "in %s", __func__);

   char *authlist = NULL;
   if (username)
      sshc->user = username;
   else
      sshc->user = getenv("USER");

   assert(sshc->user);
   logit(0, "username: %s", sshc->user);

   authlist = libssh2_userauth_list(sshc->session, sshc->user,
         strlen(sshc->user));
   assert(authlist);

   logit(0, "authentication methods: %s", authlist);

   int retval = 0;
   if (password) {
      retval = ssh_passauth(authlist, password);
   }
   else {
      if (ssh_pkauth(authlist))
         retval = 1;
      else if (ssh_kiauth(authlist))
         retval = 1;
      else if (ssh_passauth(authlist, NULL))
         retval = 1;
   }

   if (retval == 0) {
      error(0, "cannot login to %s, no other authentication method found",
            sshc->host);
   }
}

static void kbd_callback(const char *name, int name_len,
      const char *instruction, int instruction_len,
      int num_prompts, const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts,
      LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, void **abstract)
{

   logit(0, "in %s", __func__);
   (void)name;
   (void)name_len;
   (void)instruction;
   (void)instruction_len;

   if (got_password == 0) {
      char prompt[MAXLINE];
      snprintf(prompt, sizeof(prompt), "Enter password for %s on %s: ",
            sshc->user, sshc->host);
      const char *password = read_pass(prompt);
      if (num_prompts == 1) {
         responses[0].text = strdup(password);
         responses[0].length = strlen(password);
      }

      got_password = 1;
   }

   (void)prompts;
   (void)abstract;
}

